﻿using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using corpus_platform.Data;
using System.Windows.Controls;

namespace corpus_platform.ViewModels
{
    public class BaseFrameElementViewModel: BindableBase, INodeFrameElement
    {
        public BaseFrameElementViewModel(FrameElement frameElement)
        {
            this.Name = frameElement.Name;
            this.Information = frameElement.Information;
        }
        public virtual FrameElementType Type => throw new NotImplementedException();
        public string Name { get; }
        public string Information { get; }

        private List<string> lexis = new();
        public List<string> Lexis
        {
            get { return lexis; }
            set { SetProperty(ref lexis, value); }
        }

        private bool frameIsInstance = false;
        public bool FrameIsInstance
        {
            get { return frameIsInstance; }
            set { SetProperty(ref frameIsInstance, value); }
        }

        public FrameViewModel Frame { get; set; }

        public virtual void GetValue(MetaInformation metaInformation, FrameInstanceValue frameInstanceValue)
        {
            if (Lexis.Count != 0)
            {
                frameInstanceValue.ElementLexis.Add(this.Name, this.Lexis.ToArray());
            }
        }

        public virtual void InformChangeFrameStatus()
        {
            frameIsInstance = !frameIsInstance;
            RaisePropertyChanged(nameof(FrameIsInstance));
        }
    }

    public class BoolFrameElementViewModel: BaseFrameElementViewModel
    {
        public BoolFrameElementViewModel(FrameElement frameElement) : base(frameElement)
        {
        }
        public override FrameElementType Type { get; } = FrameElementType.Boolean;

        private bool storage = false;
        public bool Storage
        {
            get { return storage; }
            set { SetProperty(ref storage, value); }
        }

        private bool isFocus = false;
        public bool IsFocus
        {
            get { return isFocus; }
            set { SetProperty(ref isFocus, value); }
        }

        public override void GetValue(MetaInformation metaInformation, FrameInstanceValue frameInstanceValue)
        {
            base.GetValue(metaInformation, frameInstanceValue);
            frameInstanceValue.Element.Add(this.Name, this.Storage);
            if (this.IsFocus)
            {
                frameInstanceValue.Focus.Add(Name);
            }
        }

        private DelegateCommand commandChangeFocusStatus;
        public DelegateCommand CommandChangeFocusStatus =>
            commandChangeFocusStatus ?? (commandChangeFocusStatus = new DelegateCommand(ExecuteCommandChangeFocusStatus, CanExecuteCommandChangeFocusStatus));

        void ExecuteCommandChangeFocusStatus()
        {
            IsFocus = !IsFocus;
        }

        bool CanExecuteCommandChangeFocusStatus()
        {
            return !FrameIsInstance;
        }
    }

    public class StringFrameElementViewModel : BaseFrameElementViewModel
    {
        public StringFrameElementViewModel(FrameElement frameElement) : base(frameElement) { }

        public override FrameElementType Type { get; } = FrameElementType.String;

        private string storage;
        public string Storage
        {
            get { return storage; }
            set { SetProperty(ref storage, value); }
        }

        private bool isFocus = false;
        public bool IsFocus
        {
            get { return isFocus; }
            set { SetProperty(ref isFocus, value); }
        }

        public override void GetValue(MetaInformation metaInformation, FrameInstanceValue frameInstanceValue)
        {
            base.GetValue(metaInformation, frameInstanceValue);
            frameInstanceValue.Element.Add(this.Name, this.Storage);
            if (this.IsFocus)
            {
                frameInstanceValue.Focus.Add(Name);
            }
        }

        private DelegateCommand commandChangeFocusStatus;
        public DelegateCommand CommandChangeFocusStatus =>
            commandChangeFocusStatus ?? (commandChangeFocusStatus = new DelegateCommand(ExecuteCommandChangeFocusStatus, CanExecuteCommandChangeFocusStatus));

        void ExecuteCommandChangeFocusStatus()
        {
            IsFocus = !IsFocus;
        }

        bool CanExecuteCommandChangeFocusStatus()
        {
            return !FrameIsInstance;
        }
    }

    public class SingleFrameElementViewModel : BaseFrameElementViewModel, INodeContainer
    {
        public SingleFrameElementViewModel(FrameElement frameElement) : base(frameElement)
        {
        }
        public override FrameElementType Type => FrameElementType.Single;

        private bool allowDrop = true;
        public bool AllowDrop
        {
            get { return allowDrop; }
            set { SetProperty(ref allowDrop, value); }
        }

        private INodeStructure storage = null;
        public INodeStructure Storage
        {
            get { return storage; }
            set { 
                SetProperty(ref storage, value);
                this.Frame.CommandCreateInstance.RaiseCanExecuteChanged();
            }
        }

        private bool isFocus = false;
        public bool IsFocus
        {
            get { return isFocus; }
            set { SetProperty(ref isFocus, value); }
        }

        public Result add(INodeStructure structure)
        {
            INodeStructure item = null;
            switch (structure.Type)
            {
                case StructureType.Frame:
                    FrameViewModel frame = (FrameViewModel)structure;
                    if (frame.IsInstance)
                    {
                        item = frame;
                    }
                    else
                    {
                        return Result.Failure;
                    }
                    break;
                case StructureType.Ontology:
                    item = structure;
                    break;
                case StructureType.Root:
                    throw new Exception();
            }
            if (item != null)
            {
                if (this.storage != null)
                {
                    this.storage.ChangeParentTo(this.storage.Root);
                }
                this.Storage= item;
                return Result.Success;
            }
            else
            {
                return Result.Failure;
            }
        }

        public void remove(INodeStructure structure)
        {
            if (this.Storage != null)
            {
                this.Storage = null;
            }
            else
            {
                throw new Exception();
            }
        }

        public override void GetValue(MetaInformation metaInformation, FrameInstanceValue frameInstanceValue)
        {
            base.GetValue(metaInformation, frameInstanceValue);
            frameInstanceValue.Element.Add(this.Name, this.Storage.GetValue(metaInformation));
            if (this.IsFocus)
            {
                frameInstanceValue.Focus.Add(Name);
            }
        }

        public override void InformChangeFrameStatus()
        {
            base.InformChangeFrameStatus();
            this.Storage.AllowDrag = !this.FrameIsInstance;
            this.AllowDrop = !this.FrameIsInstance;
        }

        private DelegateCommand commandChangeFocusStatus;
        public DelegateCommand CommandChangeFocusStatus =>
            commandChangeFocusStatus ?? (commandChangeFocusStatus = new DelegateCommand(ExecuteCommandChangeFocusStatus, CanExecuteCommandChangeFocusStatus));

        void ExecuteCommandChangeFocusStatus()
        {
            IsFocus = !IsFocus;
        }

        bool CanExecuteCommandChangeFocusStatus()
        {
            return !FrameIsInstance;
        }

        public void SweepStorageToRoot()
        {
            if (this.Storage != null)
            {
                this.Storage.ChangeParentTo(this.Storage.Root);
            }
        }
    }

    public class MultipleFrameElementViewModel : BaseFrameElementViewModel, INodeContainer
    {
        public MultipleFrameElementViewModel(FrameElement frameElement) : base(frameElement)
        {
        }
        public override FrameElementType Type => FrameElementType.Multiple;

        private bool allowDrop = true;
        public bool AllowDrop
        {
            get { return allowDrop; }
            set { SetProperty(ref allowDrop, value); }
        }

        private ObservableCollection<FrameElementContainer> storage = new();
        public ObservableCollection<FrameElementContainer> Storage
        {
            get { return storage; }
            set { SetProperty(ref storage, value); }
        }

        public Result add(INodeStructure structure)
        {
            INodeStructure item = null;
            switch (structure.Type)
            {
                case StructureType.Frame:
                    FrameViewModel frame = (FrameViewModel)structure;
                    if (frame.IsInstance)
                    {
                        item = frame;
                    }
                    else
                    {
                        return Result.Failure;
                    }
                    break;
                case StructureType.Ontology:
                    item = structure;
                    break;
                case StructureType.Root:
                    throw new Exception();
            }
            if (item != null)
            {
                this.Storage.Add(new FrameElementContainer(item));
                return Result.Success;
            }
            else
            {
                return Result.Failure;
            }
        }

        public void remove(INodeStructure structure)
        {
            Storage.Remove(new FrameElementContainer(structure));
        }

        public override void GetValue(MetaInformation metaInformation, FrameInstanceValue frameInstanceValue)
        {
            base.GetValue(metaInformation, frameInstanceValue);
            List<object> values = new();
            List<object> focus = new();
            foreach (FrameElementContainer item in this.Storage)
            {
                object itemValue = item.GetValue(metaInformation);
                values.Add(itemValue);
                if (item.IsFocus)
                {
                    focus.Add(itemValue);
                }
            }
            frameInstanceValue.Element.Add(Name, values.ToArray());
            if (focus.Count > 0)
            {
                frameInstanceValue.ContainerFocus.Add(Name, focus.ToArray());
            }
        }

        public override void InformChangeFrameStatus()
        {
            base.InformChangeFrameStatus();
            this.allowDrop = !this.FrameIsInstance;
            foreach(FrameElementContainer item in this.Storage)
            {
                item.Storage.AllowDrag = !this.FrameIsInstance;
                item.FrameIsInstance = this.FrameIsInstance;
            }
        }

        public void SweepStorageToRoot()
        {
            while (this.Storage.Count != 0)
            {
                FrameElementContainer item = this.Storage[0];
                item.Storage.ChangeParentTo(item.Storage.Root);
            }
        }

        public class FrameElementContainer : BindableBase, IEquatable<FrameElementContainer>
        {
            public FrameElementContainer(INodeStructure structure)
            {
                Storage = structure;
            }

            public INodeStructure Storage { get; }

            private bool frameIsInstance;
            public bool FrameIsInstance
            {
                get { return frameIsInstance; }
                set { SetProperty(ref frameIsInstance, value); }
            }

            private bool isFocus;
            public bool IsFocus
            {
                get { return isFocus; }
                set { SetProperty(ref isFocus, value); }
            }

            public object GetValue(MetaInformation metaInformation)
            {
                return Storage.GetValue(metaInformation);
            }

            public bool Equals(FrameElementContainer other)
            {
                return Storage.Equals(other.Storage);
            }

            public void ChangeParentTo(INodeContainer container)
            {
                this.Storage.ChangeParentTo(container);
            }

            private DelegateCommand commandChangeFocusStatus;
            public DelegateCommand CommandChangeFocusStatus =>
                commandChangeFocusStatus ?? (commandChangeFocusStatus = new DelegateCommand(ExecuteCommandChangeFocusStatus, CanExecuteCommandChangeFocusStatus));

            void ExecuteCommandChangeFocusStatus()
            {
                IsFocus = !IsFocus;
            }

            bool CanExecuteCommandChangeFocusStatus()
            {
                return !FrameIsInstance;
            }
        }
    }

}
